using System;
using System.Collections.Generic;
using System.Text;

namespace Heaven.Engine.Animation
{
    /// <summary>
    /// A collection of key frames. It allows to manage 
    /// key frames, fast searching it and so on.
    /// Key frames is sorted by time
    /// </summary>
    public class KeyframeCollection : IEnumerable<Keyframe>
    {
        #region Events

        /// <summary>
        /// Occurs when the collection or one of its parts have been changed
        /// </summary>
        public event EventHandler Invalidated;

        void Invalidate()
        {
            if (Invalidated != null) Invalidated(this, EventArgs.Empty);
        }

        #endregion

        #region Fields

        // List of key frames. Initially the capacity 
        // is setted to 2 (to make faster the Add method, 
        // because an animation contains at least two key frames)
        SortedList<double, Keyframe> keyframes = new SortedList<double, Keyframe>(2);

        #endregion

        #region Properties

        /// <summary>
        /// Gets the number of keyframes
        /// </summary>
        public int Count
        {
            get { return keyframes.Count; }
        }

        /// <summary>
        /// Gets a key frame by the order number
        /// </summary>
        /// <param name="i">An order number</param>
        /// <returns>Key frame</returns>
        public Keyframe this[int i]
        {
            get
            {
                return keyframes.Values[i];
            }
        }

        /// <summary>
        /// Gets the first key frame of the collection
        /// </summary>
        public Keyframe First
        {
            get { return keyframes.Values[0]; }
        }

        /// <summary>
        /// Gets the last key frame of the collection
        /// </summary>
        public Keyframe Last
        {
            get { return keyframes.Values[keyframes.Count - 1]; }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Default constructor
        /// </summary>
        public KeyframeCollection()
        {
        }

        #endregion

        #region Methods

        /// <summary>
        /// Adds key frame to the collection
        /// </summary>
        /// <param name="keyframe">A key frame</param>
        public void Add(Keyframe keyframe)
        {
            keyframes.Add(keyframe.Time, keyframe);
            Invalidate();
        }

        /// <summary>
        /// Adds key frame to the collection
        /// </summary>
        /// <param name="state">A key state of a controller</param>
        /// <param name="time">A time stamp of the state</param>
        public Keyframe Add(object state, double time)
        {
            Keyframe keyframe = new Keyframe(state, time);
            keyframes.Add(time, keyframe);
            Invalidate();
            return keyframe;
        }

        /// <summary>
        /// Removes key frame from the collection
        /// </summary>
        /// <param name="keyframe">Key frame</param>
        public void Remove(Keyframe keyframe)
        {
            keyframes.Remove(keyframe.Time);
            Invalidate();
        }

        /// <summary>
        /// Removes key frame from the collection
        /// </summary>
        /// <param name="time">The keyframe's time</param>
        public void Remove(double time)
        {
            keyframes.Remove(time);
            Invalidate();
        }

        /// <summary>
        /// Determines whether the collection contains the given time
        /// </summary>
        /// <param name="time">Time</param>
        /// <returns>True, if the given time exists, else false</returns>
        public bool Contains(double time)
        {
            return keyframes.ContainsKey(time);
        }

        /// <summary>
        /// Gets two, one or zero key frames which bound the given time
        /// </summary>
        /// <param name="time">Time, milliseconds</param>
        /// <returns>Array of two key frames, if the time is bounded by key frames;
        /// array of one key, if the time outside, else empty array</returns>
        public Keyframe[] GetNeighbors(double time)
        {
            // Particular cases
            if (keyframes.Count < 1) return new Keyframe[] {};
            if (keyframes.Keys[0] >= time)
            {
                return new Keyframe[] { keyframes.Values[0] };
            }
            if (keyframes.Keys[keyframes.Count - 1] <= time)
            {
                return new Keyframe[] { keyframes.Values[keyframes.Count - 1] };
            }

            // Common case, the time is bounded by key frames
            for (int i = 0; i < keyframes.Count; i++)
            {
                if (keyframes.Keys[i] > time) return new Keyframe[]
                {
                    keyframes.Values[i - 1],
                    keyframes.Values[i]
                };
            }

            // Imposible case
            return null;
        }

        #endregion

        #region Enumeration

        /// <summary>
        /// Gets an enumerator
        /// </summary>
        /// <returns>An enumerator</returns>
        public IEnumerator<Keyframe> GetEnumerator()
        {
            return keyframes.Values.GetEnumerator();
        }

        /// <summary>
        /// Gets an enumerator
        /// </summary>
        /// <returns>An enumerator</returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return keyframes.Values.GetEnumerator();
        }

        #endregion
    }
}
